import { Schema, arrayOf, normalize } from 'normalizr';
import { camelizeKeys } from 'humps';
import 'isomorphic-fetch';

// Extracts the next page URL from Github API response.
function getNextPageUrl(response) {
    const link = response.headers.get('link');
    if (!link) {
        return null;
    }

    const nextLink = link.split(',').find(s => s.indexOf('rel="next"') > -1);
    if (!nextLink) {
        return null;
    }

    return nextLink.split(';')[0].slice(1, -1);
}

const API_ROOT = 'https://api.github.com/';

// Fetches an API response and normalizes the result JSON according to schema.
// This makes every API response have the same shape, regardless of how nested it was.
function callApi(endpoint, schema) {
    const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT + endpoint : endpoint;

    return fetch(fullUrl)
        .then(response =>
            response.json().then(json => ({ json, response }))
        ).then(({ json, response }) => {
            if (!response.ok) {
                return Promise.reject(json);
            }

            // humps.camelizeKeys(object, options) 把对象的键值转换成 驼峰命名
            // 例如:
            // {"login":"qqqq","id":8185686,"node_id":"MDQ6VXNlcjgxODU2ODY=","avatar_url":"https://avatars3.githubusercontent.com/u/8185686?v=4","gravatar_id":"","url":"https://api.github.com/users/qqqq","html_url":"https://github.com/qqqq","followers_url":"https://api.github.com/users/qqqq/followers","following_url":"https://api.github.com/users/qqqq/following{/other_user}","gists_url":"https://api.github.com/users/qqqq/gists{/gist_id}","starred_url":"https://api.github.com/users/qqqq/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/qqqq/subscriptions","organizations_url":"https://api.github.com/users/qqqq/orgs","repos_url":"https://api.github.com/users/qqqq/repos","events_url":"https://api.github.com/users/qqqq/events{/privacy}","received_events_url":"https://api.github.com/users/qqqq/received_events","type":"User","site_admin":false,"name":null,"company":null,"blog":"","location":null,"email":null,"hireable":null,"bio":null,"twitter_username":null,"public_repos":0,"public_gists":0,"followers":0,"following":0,"created_at":"2014-07-17T01:07:59Z","updated_at":"2016-02-27T17:46:11Z"}
            // 转后: {"login":"qqqq","id":8185686,"nodeId":"MDQ6VXNlcjgxODU2ODY=","avatarUrl":"https://avatars3.githubusercontent.com/u/8185686?v=4","gravatarId":"","url":"https://api.github.com/users/qqqq","htmlUrl":"https://github.com/qqqq","followersUrl":"https://api.github.com/users/qqqq/followers","followingUrl":"https://api.github.com/users/qqqq/following{/other_user}","gistsUrl":"https://api.github.com/users/qqqq/gists{/gist_id}","starredUrl":"https://api.github.com/users/qqqq/starred{/owner}{/repo}","subscriptionsUrl":"https://api.github.com/users/qqqq/subscriptions","organizationsUrl":"https://api.github.com/users/qqqq/orgs","reposUrl":"https://api.github.com/users/qqqq/repos","eventsUrl":"https://api.github.com/users/qqqq/events{/privacy}","receivedEventsUrl":"https://api.github.com/users/qqqq/received_events","type":"User","siteAdmin":false,"name":null,"company":null,"blog":"","location":null,"email":null,"hireable":null,"bio":null,"twitterUsername":null,"publicRepos":0,"publicGists":0,"followers":0,"following":0,"createdAt":"2014-07-17T01:07:59Z","updatedAt":"2016-02-27T17:46:11Z"}
            const camelizedJson = camelizeKeys(json);
            const nextPageUrl = getNextPageUrl(response);

            return Object.assign({},
                normalize(camelizedJson, schema),
                { nextPageUrl }
            );
        });
}

// We use this Normalizr schemas to transform API responses from a nested form
// to a flat form where repos and users are placed in `entities`, and nested
// JSON objects are replaced with their IDs. This is very convenient for
// consumption by reducers, because we can easily build a normalized tree
// and keep it updated as we fetch more data.

// Read more about Normalizr: https://github.com/gaearon/normalizr

const userSchema = new Schema('users', {
    idAttribute: 'login',
});

const repoSchema = new Schema('repos', {
    idAttribute: 'fullName',
});

repoSchema.define({
    owner: userSchema,
});

// Schemas for Github API responses.
export const Schemas = {
    USER: userSchema,
    USER_ARRAY: arrayOf(userSchema),
    REPO: repoSchema,
    REPO_ARRAY: arrayOf(repoSchema),
};

// Action 中存在该 Key, 才能被该中间件处理
//
// CALL_API 是 Symbol类型。Symbol是一个唯一的、不变的标识，可以用作对象的key。它是为了避免命名冲突而生的，
// 这里也可以用字符串来代替，不过你就要自己保证命名不发生冲突了。
export const CALL_API = Symbol('Call API');


// 分析中间件执行流程 node_modules/redux/src/applyMiddleware.js
/*
//参数 middlewares: 使用展开语法，将我们写进去的中间件，生成一个中间件数组middlewares

export default function applyMiddleware(...middlewares){
  return (createStore)=＞(reducer, initialState, enhancer)=＞{
    var store=createStore(reducer, initialState, enhancer)
    var dispatch=store.dispatch
  var chain=[]
  var middlewareAPI={
    getState:store.getState,
    dispatch:(action)=＞dispatch(action)
  }

  //1.  中间件最外层函数的执行，也就是参数为store那一次。这里的 store 是middlewareAPI：
  chain=middlewares.map(middleware=＞middleware(middlewareAPI))

  //2.  中间件第二层, 即next参数。这里 next 是 store.dispatch。
  //    chain中的每一项元素都是中间件的第二层函数，将其用逗号隔开放进compose参数里，生成一个嵌套函数。
  //    store.dispatch是整个嵌套函数的入口参数，它是一个未经任何加工的dispatch，这个可怜的家伙进去后，将被中间件层层加工，经历风雨，变成拥有更多功能的高级dispatch。
  //    这个过程使用了函数式编程中的管道（Pipe）思想，即数据被函数列表依次加工，就像管道一样，最后返回我们想要的结果。
  dispatch=compose(...chain)(store.dispatch)

  //3. 生成加工后的 dispatch, 给外部调用。
  //   中间件第三层,执行不在applyMiddleware中。当你外部 使用新的dispatch方法发起action时，第三层action函数才被执行。
  return{
    ...store,
    dispatch
  }
 }
}
* */

// Redux中间件，能处理Type类型为 CALL_API 的Action.
export default store => next => action => {
    //store: {
    //     getState:store.getState,
    //     dispatch:(action)=＞dispatch(action)
    //   }
    //next: 原始的 store.dispatch
    //
    //action: 外部传入各种 action（各种type值）

    // 处理一系列action对象之前，首先要过滤掉不符合指定规则的action，并做异常处理，也就是消毒

    const callAPI = action[CALL_API];
    if (typeof callAPI === 'undefined') {
        //不包含 CALL_API， 该中间件不能处理
        return next(action);
    }

    //各种异常处理
    let { endpoint } = callAPI;
    const { schema, types } = callAPI;

    if (typeof endpoint === 'function') {
        endpoint = endpoint(store.getState());
    }
    if (typeof endpoint !== 'string') {
        throw new Error('Specify a string endpoint URL.');
    }
    if (!schema) {
        throw new Error('Specify one of the exported Schemas.');
    }
    if (!Array.isArray(types) || types.length !== 3) {
        throw new Error('Expected an array of three action types.');
    }
    if (!types.every(type => typeof type === 'string')) {
        throw new Error('Expected action types to be strings.');
    }

    // 接着执行请求，并在开始请求、请求成功或者请求失败后各使用next()发起相应的action
    function actionWith(data) {
        const finalAction = Object.assign({}, action, data);
        delete finalAction[CALL_API];
        return finalAction;
    }

    const [requestType, successType, failureType] = types;

    //dispatch(redux标准的action), 通知外部开始请求数据了(此时UI应该显示 Loading)
    next(actionWith({ type: requestType }));

    //请求数据，然后按照结果类型(成功或失败), dispatch(redux标准的action)
    return callApi(endpoint, schema).then(
        //按照redux action 标准 {type:'XXXX', ...}
        response => next(actionWith({
            response,
            type: successType,
        })),
        error => next(actionWith({
            type: failureType,
            error: error.message || 'Something bad happened',
        }))
    );
};
// API中间件就编写完成了。经过API中间件的加工，发起携带CALL_API的action创建函数就变成了一系列的流程.
/*
                获取action的信息
                     ⬇
                    消毒
                     ⬇
CALL_API -->  发起开始请求的action
                     ⬇
                执行fetch请求
                     ⬇
                  请求结束后,
              发起成功或失败的action
*/

// 总结:
//     1. 自定义Redux中间件只需要编写一个三层的嵌套函数。
//     2. 一个异步请求通常需要编写三个action，分别在开始请求、请求成功和请求失败时被发起。
